#include <dlfcn.h>

#include "Core.h"
#include "Debug.h"
#include "MemAccess.h"

#include "Offsets.h"

namespace DFHack
{
  DBG_DECLARE(i18n, offsets, DebugCategory::LDEBUG);
}

using std::vector;
using namespace DFHack;
using namespace DFHack::I18n;

extern char *plugin_name;

#define SYM_ADDST "_ZN9graphicst5addstERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE13justificationi"
DFhackDataExport void *addst = NULL;
#define SYM_ADDST_FLAG "_ZN9graphicst10addst_flagERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE13justificationij"
DFhackDataExport void *addst_flag = NULL;
#define SYM_ADDCOLOREDST "_ZN9graphicst12addcoloredstEPKcS1_"
DFhackDataExport void *addcoloredst = NULL;
#define SYM_TOP_ADDST "_ZN9graphicst9top_addstERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE13justificationi"
DFhackDataExport void *top_addst = NULL;
#define SYM_NINESLICES "_Z14draw_nineslicePiiiiih"
DFhackDataExport void *draw_nineslice = NULL;
#define SYM_HORIZONTAL_NINESLICE "_Z25draw_horizontal_nineslicePiiiiih"
DFhackDataExport void *draw_horizontal_nineslice = NULL;
#define SYM_GPS_ALLOCATE "_ZN8renderer12gps_allocateEiiiiii"
DFhackDataExport void *gps_allocate = NULL;
#define SYM_UPDATE_ALL "_ZN16renderer_2d_base10update_allEv"
DFhackDataExport void *update_all = NULL;
#define SYM_UPDATE_TILE "_ZN16renderer_2d_base11update_tileEii"
DFhackDataExport void *update_tile = NULL;
#define SYM_GET_KEY_DISPLAY "_ZN15enabler_inputst13GetKeyDisplayB5cxx11Ei"
DFhackDataExport void *get_key_display = NULL;
static const unsigned char pattern_mtb_process_string_to_lines[] = {
    0xF3, 0x0F, 0x1E, 0xFA,                   // endbr64
    0x41, 0x57,                               // push    r15
    0x41, 0x56,                               // push    r14
    0x41, 0x55,                               // push    r13
    0x41, 0x54,                               // push    r12
    0x55,                                     // push    rbp
    0x48, 0x89, 0xFD,                         // mov     rbp, rdi
    0x53,                                     // push    rbx
    0x48, 0x81, 0xEC, 0x68, 0x0D, 0x00, 0x00, // sub     rsp, 0D68h
};
DFhackDataExport void *mtb_process_string_to_lines = NULL;
static const unsigned char pattern_mtb_set_width[] = {
    0xF3, 0x0F, 0x1E, 0xFA, // endbr64
    0x39, 0x77, 0x30,       // cmp     [rdi+30h], esi
    0x0F, 0x84,             // jz      ???
};
DFhackDataExport void *mtb_set_width = NULL;
static const unsigned char pattern_render_help_dialog[] = {
    0xF3, 0x0F, 0x1E, 0xFA,                   // endbr64
    0x41, 0x57,                               // push    r15
    0x41, 0x56,                               // push    r14
    0x49, 0x89, 0xFE,                         // mov     r14, rdi
    0x41, 0x55,                               // push    r13
    0x41, 0x54,                               // push    r12
    0x55,                                     // push    rbp
    0x53,                                     // push    rbx
    0x48, 0x81, 0xEC, 0xB8, 0x00, 0x00, 0x00, // sub     rsp, 0B8h
};
DFhackDataExport void *render_help_dialog = NULL;

static void dumpRangeOffset(color_ostream &out, void *result)
{
  vector<t_memrange> ranges;
  Core::getInstance().p->getMemRanges(ranges);
  for (size_t i = 0; i < ranges.size(); i++)
  {
    t_memrange &range = ranges[i];
    if (range.isInRange(result))
    {
      size_t offset = ((size_t)result - (size_t)range.base);
      DEBUG(offsets, out).print("[%s] found offset: 0x%lx@0x%lx\n", plugin_name, offset, (size_t)result);
    }
  }
}

static bool searchByPattern(color_ostream &out, const char *name, const unsigned char *pattern, size_t len, void **result)
{
  vector<t_memrange> ranges;
  DEBUG(offsets, out).print("[%s] searching for pattern %s...\n", plugin_name, name);
  Core::getInstance().p->getMemRanges(ranges);

  *result = NULL;
  for (size_t i = 0; i < ranges.size(); i++)
  {
    t_memrange &range = ranges[i];

    if (!range.read || !strstr(range.name, "dwarfort"))
    {
      continue;
    }

    for (*result = range.base; (size_t)(*result) + len < (size_t)(range.start); *result = (char *)(*result) + 16)
    {
      if (!memcmp(*result, pattern, len))
      {
        size_t offset = ((size_t)(*result) - (size_t)range.base);
        DEBUG(offsets, out).print("[%s] found offset: 0x%lx@0x%lx\n", plugin_name, offset, (size_t)(*result));
        return true;
      }
    }
  }

  return false;
}

static bool searchBySymbol(color_ostream &out, const char *symbol, void **result)
{
  DEBUG(offsets, out).print("[%s] searching for symbol %s...\n", plugin_name, symbol);
  *result = dlsym(RTLD_DEFAULT, symbol);
  if (!*result)
  {
    const char *error = dlerror();
    DEBUG(offsets, out).print("[%s] error searching for symbol %s: %s\n", plugin_name, symbol, error ? error : "unknown");
    return false;
  }

  dumpRangeOffset(out, *result);

  return true;
}

bool Offsets::searchAll(color_ostream &out)
{
  return searchBySymbol(out, SYM_ADDST, &addst) &&
         searchBySymbol(out, SYM_ADDST_FLAG, &addst_flag) &&
         searchBySymbol(out, SYM_ADDCOLOREDST, &addcoloredst) &&
         searchBySymbol(out, SYM_TOP_ADDST, &top_addst) &&
         searchBySymbol(out, SYM_NINESLICES, &draw_nineslice) &&
         searchBySymbol(out, SYM_HORIZONTAL_NINESLICE, &draw_horizontal_nineslice) &&
         searchBySymbol(out, SYM_GPS_ALLOCATE, &gps_allocate) &&
         searchBySymbol(out, SYM_UPDATE_ALL, &update_all) &&
         searchBySymbol(out, SYM_UPDATE_TILE, &update_tile) &&
         searchBySymbol(out, SYM_GET_KEY_DISPLAY, &get_key_display) &&
         searchByPattern(out, "mtb_process_string_to_lines", pattern_mtb_process_string_to_lines, sizeof(pattern_mtb_process_string_to_lines), &mtb_process_string_to_lines) &&
         searchByPattern(out, "mtb_set_width", pattern_mtb_set_width, sizeof(pattern_mtb_set_width), &mtb_set_width) &&
         searchByPattern(out, "render_help_dialog", pattern_render_help_dialog, sizeof(pattern_render_help_dialog), &render_help_dialog);
}
